import {ParseException, Span, TokenType} from './index.js'
import JavaClass from '../editor/java-class.js'
import {
    AsyncCall,
    BinaryOperation,
    Break,
    ClassConverter,
    Continue,
    Exit,
    ForStatement,
    FunctionCall,
    IfStatement,
    Import,
    LambdaFunction,
    LinqField,
    LinqJoin,
    LinqOrder,
    LinqSelect,
    ListLiteral,
    Literal,
    MapLiteral,
    MapOrArrayAccess,
    MemberAccess,
    MethodCall,
    NewStatement,
    Return,
    Spread,
    TernaryOperation,
    TryStatement,
    UnaryOperation,
    VarDefine,
    VariableAccess,
    WhileStatement,
    WholeLiteral,
    LanguageExpression
} from './ast.js'

export const keywords = ["import", "as", "var", "return", "break", "continue", "if", "for", "in", "new", "true", "false", "null", "else", "try", "catch", "finally", "async", "while"];
export const linqKeywords = ["from", "join", "left", "group", "by", "as", "having", "and", "or", "in", "where", "on"];
const binaryOperatorPrecedence = [
    [TokenType.Assignment],
    [TokenType.PlusEqual, TokenType.MinusEqual, TokenType.AsteriskEqual, TokenType.ForwardSlashEqual, TokenType.PercentEqual],
    [TokenType.Or, TokenType.And, TokenType.SqlOr, TokenType.SqlAnd, TokenType.Xor],
    [TokenType.EqualEqualEqual, TokenType.Equal, TokenType.NotEqualEqual, TokenType.NotEqual, TokenType.SqlNotEqual],
    [TokenType.Less, TokenType.LessEqual, TokenType.Greater, TokenType.GreaterEqual],
    [TokenType.Plus, TokenType.Minus],
    [TokenType.ForwardSlash, TokenType.Asterisk, TokenType.Percentage]
];
const linqBinaryOperatorPrecedence = [
    [TokenType.PlusEqual, TokenType.MinusEqual, TokenType.AsteriskEqual, TokenType.ForwardSlashEqual, TokenType.PercentEqual],
    [TokenType.Or, TokenType.And, TokenType.SqlOr, TokenType.SqlAnd, TokenType.Xor],
    [TokenType.Assignment, TokenType.EqualEqualEqual, TokenType.Equal, TokenType.NotEqualEqual, TokenType.Equal, TokenType.NotEqual, TokenType.SqlNotEqual],
    [TokenType.Less, TokenType.LessEqual, TokenType.Greater, TokenType.GreaterEqual],
    [TokenType.Plus, TokenType.Minus],
    [TokenType.ForwardSlash, TokenType.Asterisk, TokenType.Percentage]
]
const unaryOperators = [TokenType.Not, TokenType.PlusPlus, TokenType.MinusMinus, TokenType.Plus, TokenType.Minus];

export class Parser {
    constructor(stream) {
        this.stream = stream;
        this.linqLevel = 0;
    }

    parse(ignoreError) {
        let nodes = [];
        try {
            while (this.stream.hasMore()) {
                let node = this.parseStatement();
                if (node != null) {
                    this.validateNode(node);
                    nodes.push(node);
                }
            }
        } catch (e) {
            if (ignoreError !== true) {
                throw e;
            }
            //console.error(e)
        }
        return nodes;
    }

    validateNode(node) {
        if (node instanceof Literal) {
            throw new ParseException('literal cannot be used alone', node.getSpan());
        }
    }

    parseStatement(expectRightCurly) {
        let result = null;
        if (this.stream.match("import", false)) {
            result = this.parseImport();
        } else if (this.stream.match("var", false)) {
            result = this.parseVarDefine();
        } else if (this.stream.match("if", false)) {
            result = this.parseIfStatement();
        } else if (this.stream.match("return", false)) {
            result = this.parseReturn();
        } else if (this.stream.match("for", false)) {
            result = this.parseForStatement();
        } else if (this.stream.match("while", false)) {
            result = this.parseWhileStatement();
        } else if (this.stream.match("continue", false)) {
            result = new Continue(this.stream.consume().getSpan());
        } else if (this.stream.match("async", false)) {
            result = this.parseAsync();
        } else if (this.stream.match("try", false)) {
            result = this.parseTryStatement();
        } else if (this.stream.match("break", false)) {
            result = new Break(this.stream.consume().getSpan());
        } else if (this.stream.match("exit", false)) {
            result = this.parseExit();
        } else {
            result = this.parseExpression(expectRightCurly);
        }
        while (this.stream.match(";", true)) {

        }
        return result;
    }

    parseConverterOrAccessOrCall(expression) {
        while (this.stream.match([TokenType.Period, TokenType.QuestionPeriod, TokenType.ColonColon], false)) {
            if (this.stream.match(TokenType.ColonColon, false)) {
                let open = this.stream.consume().getSpan();
                let args = [];
                let identifier = this.stream.expect(TokenType.Identifier);
                let closing = identifier.getSpan();
                if (this.stream.match(TokenType.LeftParantheses, false)) {
                    args = this.parseArguments();
                    closing = this.stream.expect(TokenType.RightParantheses).getSpan();
                }
                expression = new ClassConverter(new Span(open, closing), identifier.getText(), expression, args);
            } else {
                expression = this.parseAccessOrCall(stream, expression);
            }
        }
        return expression;
    }

    checkKeyword(span) {
        if (keywords.indexOf(span.getText()) > -1) {
            throw new ParseException('变量名不能定义为关键字', span);
        }
    }

    parseExit() {
        let opening = this.stream.expect("exit").getSpan();
        let expressionList = [];
        do {
            expressionList.push(this.parseExpression());
        } while (this.stream.match(TokenType.Comma, true));
        return new Exit(new Span(opening, this.stream.getPrev().getSpan()), expressionList);
    }

    parseImport() {
        let opening = this.stream.expect("import").getSpan();
        if (this.stream.hasMore()) {
            let expected = this.stream.consume();
            let varName;
            let module = expected.getTokenType() === TokenType.Identifier
            if (expected.getTokenType() === TokenType.StringLiteral || module) {
                if (expected.getTokenType() === TokenType.StringLiteral) {
                    if (this.stream.match("as", true)) {
                        varName = this.stream.expect(TokenType.Identifier);
                        this.checkKeyword(varName.getSpan());
                    }
                }
                return new Import(new Span(opening, expected.getSpan()), expected.getSpan(), module);
            } else {
                throw new ParseException("Expected identifier or string, but got stream is " + expected.getTokenType().error, this.stream.getPrev().getSpan());
            }
        }
        throw new ParseException("Expected identifier or string, but got stream is EOF", this.stream.getPrev().getSpan());
    }

    parseReturn() {
        let returnSpan = this.stream.expect("return").getSpan();
        if (this.stream.match(";", false)) return new Return(returnSpan, null);
        let returnValue = this.parseExpression();
        return new Return(new Span(returnSpan, returnValue.getSpan()), returnValue);
    }

    parseAsync() {
        let opening = this.stream.expect("async").getSpan();
        let expression = this.parseExpression();
        if (expression instanceof MethodCall || expression instanceof FunctionCall || expression instanceof LambdaFunction) {
            return new AsyncCall(new Span(opening, this.stream.getPrev().getSpan()), expression);
        }
        throw new ParseException("Expected MethodCall or FunctionCall or LambdaFunction", this.stream.getPrev().getSpan())
    }

    parseIfStatement() {
        let openingIf = this.stream.expect("if").getSpan();
        let condition = this.parseExpression();
        let trueBlock = this.parseFunctionBody();
        let elseIfs = [];
        let falseBlock = [];
        while (this.stream.hasMore() && this.stream.match("else", true)) {
            if (this.stream.hasMore() && this.stream.match("if", false)) {
                let elseIfOpening = this.stream.expect("if").getSpan();
                let elseIfCondition = this.parseExpression();
                let elseIfBlock = this.parseFunctionBody();
                let elseIfSpan = new Span(elseIfOpening, elseIfBlock.length > 0 ? elseIfBlock[(elseIfBlock.length - 1)].getSpan() : elseIfOpening);
                elseIfs.push(new IfStatement(elseIfSpan, elseIfCondition, elseIfBlock, [],));
            } else {
                falseBlock = falseBlock.concat(this.parseFunctionBody());
                break;
            }
        }
        let closingEnd = this.stream.getPrev().getSpan();

        return new IfStatement(new Span(openingIf, closingEnd), condition, trueBlock, elseIfs, falseBlock);
    }

    parseNewExpression(opening) {
        let identifier = this.stream.expect(TokenType.Identifier);
        let args = this.parseArguments();
        let closing = this.stream.expect(")").getSpan();
        return this.parseConverterOrAccessOrCall(new NewStatement(new Span(opening, closing), identifier.getText(), args));
    }

    parseArguments() {
        this.stream.expect(TokenType.LeftParantheses);
        let args = [];
        while (this.stream.hasMore() && !this.stream.match(TokenType.RightParantheses, false)) {
            args.push(this.parseExpression());
            if (!this.stream.match(TokenType.RightParantheses, false)) this.stream.expect(TokenType.Comma);
        }
        return args;
    }

    parseForStatement() {
        let openingFor = this.stream.expect("for").getSpan();
        this.stream.expect("(");
        let index = null;
        let value = this.stream.expect(TokenType.Identifier).getSpan();
        this.checkKeyword(value);
        if (this.stream.match(TokenType.Comma, true)) {
            index = value;
            value = this.stream.expect(TokenType.Identifier).getSpan();
            this.checkKeyword(value);
        }
        this.stream.expect("in");
        let mapOrArray = this.parseExpression();
        this.stream.expect(")");
        let body = this.parseFunctionBody();
        return new ForStatement(new Span(openingFor, this.stream.getPrev().getSpan()), index && index.getText(), value && value.getText(), mapOrArray, body);
    }

    parseVarDefine() {
        let opening = this.stream.expect("var").getSpan();
        let token = this.stream.expect(TokenType.Identifier);
        this.checkKeyword(token.getSpan());
        if (this.stream.match(TokenType.Assignment, true)) {
            return new VarDefine(new Span(opening, this.stream.getPrev().getSpan()), token.getText(), this.parseExpression());
        }
        return new VarDefine(new Span(opening, this.stream.getPrev().getSpan()), token.getText(), null);
    }

    parseTryStatement() {
        let opening = this.stream.expect("try");
        let tryBlocks = this.parseFunctionBody();
        let catchBlocks = [];
        let finallyBlocks = [];
        let exception = null;
        if (this.stream.match("catch", true)) {
            if (this.stream.match("(", true)) {
                exception = this.stream.expect(TokenType.Identifier).getText();
                this.stream.expect(")");
            }
            catchBlocks = catchBlocks.concat(this.parseFunctionBody());
        }
        if (this.stream.match("finally", true)) {
            finallyBlocks = finallyBlocks.concat(this.parseFunctionBody());
        }
        return new TryStatement(new Span(opening.getSpan(), this.stream.getPrev().getSpan()), exception, tryBlocks, catchBlocks, finallyBlocks);
    }

    parseWhileStatement() {
        let openingWhile = this.stream.expect("while").getSpan();
        let condition = this.parseExpression();
        let trueBlock = this.parseFunctionBody();
        let closingEnd = this.stream.getPrev().getSpan();

        return new WhileStatement(new Span(openingWhile, closingEnd), condition, trueBlock);
    }

    parseFunctionBody() {
        this.stream.expect("{");
        let blocks = [];
        while (this.stream.hasMore() && !this.stream.match("}", false)) {
            let node = this.parseStatement(true);
            if (node != null) {
                this.validateNode(node);
                blocks.push(node);
            }
        }
        this.expectCloseing();
        return blocks;
    }

    expectCloseing() {
        if (!this.stream.hasMore()) {
            throw new ParseException("Did not find closing }.", this.stream.prev().getSpan());
        }
        return this.stream.expect("}").getSpan();
    }

    parseExpression(expectRightCurly) {
        return this.parseTernaryOperator(expectRightCurly);
    }

    parseTernaryOperator(expectRightCurly) {
        let condition = this.parseBinaryOperator(0, expectRightCurly);
        if (this.stream.match(TokenType.Questionmark, true)) {
            let trueExpression = this.parseTernaryOperator(expectRightCurly);
            this.stream.expect(TokenType.Colon);
            let falseExpression = this.parseTernaryOperator(expectRightCurly);
            if (condition instanceof BinaryOperation && condition.getOperator() === TokenType.Assignment) {
                condition.setRightOperand(new TernaryOperation(condition.getRightOperand(), trueExpression, falseExpression));
                return condition;
            }
            return new TernaryOperation(condition, trueExpression, falseExpression);
        } else {
            return condition;
        }
    }

    parseBinaryOperator(level, expectRightCurly) {
        let nextLevel = level + 1;
        let precedence = this.linqLevel > 0 ? linqBinaryOperatorPrecedence : binaryOperatorPrecedence;
        let left = nextLevel === precedence.length ? this.parseUnaryOperator(expectRightCurly) : this.parseBinaryOperator(nextLevel, expectRightCurly);
        let operators = precedence[level];
        while (this.stream.hasMore() && this.stream.match(operators, false)) {
            let operator = this.stream.consume();
            if (operator.type.inLinq && this.linqLevel === 0) {
                throw new ParseException(operator.getText() + " 只能在Linq中使用", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
            }
            let right = nextLevel === precedence.length ? this.parseUnaryOperator(expectRightCurly) : this.parseBinaryOperator(nextLevel, expectRightCurly);
            left = new BinaryOperation(left, operator, right, this.linqLevel);
        }
        return left;
    }

    parseUnaryOperator(expectRightCurly) {
        if (this.stream.match(unaryOperators, false)) {
            return new UnaryOperation(this.stream.consume(), this.parseUnaryOperator(expectRightCurly));
        } else {
            if (this.stream.match(TokenType.LeftParantheses, false)) {    //(
                let openSpan = this.stream.expect(TokenType.LeftParantheses).getSpan();
                let index = this.stream.makeIndex();
                let parameters = [];
                while (this.stream.match(TokenType.Identifier, false)) {
                    let identifier = this.stream.expect(TokenType.Identifier);
                    parameters.push(identifier.getSpan().getText());
                    if (this.stream.match(TokenType.Comma, true)) { //,
                        continue;
                    }
                    if (this.stream.match(TokenType.RightParantheses, true)) {  //)
                        if (this.stream.match(TokenType.Lambda, true)) {   // =>
                            return this.parseLambdaBody(openSpan, parameters);
                        }
                        break;
                    }
                }
                if (this.stream.match(TokenType.RightParantheses, true) && this.stream.match(TokenType.Lambda, true)) {
                    return this.parseLambdaBody(openSpan, parameters);
                }
                this.stream.resetIndex(index);
                let expression = this.parseExpression();
                if (this.stream.match([TokenType.Period, TokenType.QuestionPeriod], false)) {
                    expression = this.parseAccessOrCall(expression);
                }
                this.stream.expect(TokenType.RightParantheses);
                return this.parseConverterOrAccessOrCall(expression);
            } else {
                let expression = this.parseAccessOrCallOrLiteral(expectRightCurly);
                if (expression instanceof MemberAccess || expression instanceof VariableAccess || expression instanceof MapOrArrayAccess) {
                    if (this.stream.match([TokenType.PlusPlus, TokenType.MinusMinus], false)) {
                        return new UnaryOperation(this.stream.consume(), expression);
                    }
                }
                return expression;
            }
        }
    }

    parseLambdaBody(openSpan, parameters) {
        let index = this.stream.makeIndex();
        let childNodes = [];
        try {
            let expression = this.parseExpression();
            childNodes.push(new Return(new Span("return", 0, 6), expression));
            return new LambdaFunction(new Span(openSpan, expression.getSpan()), parameters, childNodes);
        } catch (e) {
            this.stream.resetIndex(index);
            if (this.stream.match(TokenType.LeftCurly, true)) {
                while (this.stream.hasMore() && !this.stream.match("}", false)) {
                    let node = this.parseStatement(true);
                    this.validateNode(node);
                    childNodes.push(node);
                }
                let closeSpan = this.expectCloseing();
                return new LambdaFunction(new Span(openSpan, closeSpan), parameters, childNodes);
            } else {
                let node = this.parseStatement();
                childNodes.push(new Return(new Span("return", 0, 6), node));
                return new LambdaFunction(new Span(openSpan, node.getSpan()), parameters, childNodes);
            }
        }
    }

    parseSpreadAccess(spread) {
        if (!spread) {
            spread = this.stream.expect(TokenType.Spread);
        }
        let target = this.parseExpression();
        return new Spread(new Span(spread.getSpan(), target.getSpan()), target);
    }

    parseAccessOrCall(target) {
        if (target === TokenType.StringLiteral || target === TokenType.Identifier) {
            let identifier = this.stream.expect(target).getSpan();
            if (target === TokenType.Identifier && "new" === identifier.getText()) {
                return this.parseNewExpression(identifier);
            }
            if (target === TokenType.Identifier && this.stream.match(TokenType.Lambda, true)) {
                return this.parseLambdaBody(identifier, [identifier.getText()]);
            }
            let result = target === TokenType.StringLiteral ? new Literal(identifier, 'java.lang.String') : new VariableAccess(identifier, identifier.getText());
            return this.parseAccessOrCall(result);
        } else {
            while (this.stream.hasMore() && this.stream.match([TokenType.LeftParantheses, TokenType.LeftBracket, TokenType.Period, TokenType.QuestionPeriod], false)) {
                // function or method call
                if (this.stream.match(TokenType.LeftParantheses, false)) {
                    let args = this.parseArguments();
                    let closingSpan = this.stream.expect(TokenType.RightParantheses).getSpan();
                    if (target instanceof VariableAccess || target instanceof MapOrArrayAccess)
                        target = new FunctionCall(new Span(target.getSpan(), closingSpan), target, args, this.linqLevel > 0);
                    else if (target instanceof MemberAccess) {
                        target = new MethodCall(new Span(target.getSpan(), closingSpan), target, args, this.linqLevel > 0);
                    } else {
                        throw new ParseException("Expected a variable, field or method.", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
                    }
                }

                // map or array access
                else if (this.stream.match(TokenType.LeftBracket, true)) {
                    let keyOrIndex = this.parseExpression();
                    let closingSpan = this.stream.expect(TokenType.RightBracket).getSpan();
                    target = new MapOrArrayAccess(new Span(target.getSpan(), closingSpan), target, keyOrIndex);
                }

                // field or method access
                else if (this.stream.match([TokenType.Period, TokenType.QuestionPeriod], false)) {
                    let optional = this.stream.consume().getTokenType() === TokenType.QuestionPeriod;
                    if (this.linqLevel > 0 && this.stream.match(TokenType.Asterisk, false)) {
                        target = new MemberAccess(target, optional, this.stream.expect(TokenType.Asterisk).getSpan(), true);
                    } else {
                        let name = this.stream.expect([TokenType.Identifier,TokenType.SqlAnd,TokenType.SqlOr]).getSpan()
                        target = new MemberAccess(new Span(target.getSpan(), name), target, optional, name, false);
                    }
                }
            }
            return target;
        }

    }

    parseMapLiteral() {
        let openCurly = this.stream.expect(TokenType.LeftCurly).getSpan();

        let keys = [];
        let values = [];
        while (this.stream.hasMore() && !this.stream.match("}", false)) {
            let key;
            if (this.stream.hasPrev()) {
                let prev = this.stream.getPrev();
                if (this.stream.match(TokenType.Spread, false) && (prev.getTokenType() == TokenType.LeftCurly || prev.getTokenType() == TokenType.Comma)) {
                    let spread = this.stream.expect(TokenType.Spread);
                    keys.push(spread);
                    values.push(this.parseSpreadAccess(spread));
                    if (this.stream.match([TokenType.Comma, TokenType.RightCurly], false)) {
                        this.stream.match(TokenType.Comma, true);
                    }
                    continue;
                }
            }
            if (this.stream.match(TokenType.StringLiteral, false)) {
                key = this.stream.expect(TokenType.StringLiteral);
            } else {
                key = this.stream.expect(TokenType.Identifier);
            }
            keys.push(key);
            if (this.stream.match([TokenType.Comma, TokenType.RightCurly], false)) {
                this.stream.match(TokenType.Comma, true);
                if (key.getTokenType() === TokenType.Identifier) {
                    values.push(new VariableAccess(key.getSpan(), key.getText()));
                } else {
                    values.push(new StringLiteral(key.getSpan(), 'java.lang.String'));
                }
            } else {
                this.stream.expect(":");
                values.push(this.parseExpression());
                if (!this.stream.match("}", false)) {
                    this.stream.expect(TokenType.Comma);
                }
            }
        }
        let closeCurly = this.stream.expect("}").getSpan();
        return new MapLiteral(new Span(openCurly, closeCurly), keys, values);
    }

    parseListLiteral() {
        let openBracket = this.stream.expect(TokenType.LeftBracket).getSpan();
        let values = [];
        while (this.stream.hasMore() && !this.stream.match(TokenType.RightBracket, false)) {
            values.push(this.parseExpression());
            if (!this.stream.match(TokenType.RightBracket, false)) {
                this.stream.expect(TokenType.Comma);
            }
        }

        let closeBracket = this.stream.expect(TokenType.RightBracket).getSpan();
        return this.parseConverterOrAccessOrCall(new ListLiteral(new Span(openBracket, closeBracket), values));
    }

    parseSelect() {
        let opeing = this.stream.expect("select", true).getSpan();
        this.linqLevel++;
        let fields = this.parseLinqFields();
        this.stream.expect("from", true);
        let from = this.parseLinqField();
        let joins = this.parseLinqJoins();
        let where;
        if (this.stream.match("where", true, true)) {
            where = this.parseExpression();
        }
        let groups = this.parseGroup();
        let having;
        if (this.stream.match("having", true, true)) {
            having = this.parseExpression();
        }
        let orders = this.parseLinqOrders();
        this.linqLevel--;
        let close = this.stream.getPrev().getSpan();
        return new LinqSelect(new Span(opeing, close), fields, from, joins, where, groups, having, orders);
    }

    parseGroup() {
        let groups = [];
        if (this.stream.match("group", true, true)) {
            this.stream.expect("by", true);
            do {
                let expression = this.parseExpression();
                groups.push(new LinqField(expression.getSpan(), expression, null));
            } while (this.stream.match(TokenType.Comma, true));
        }
        return groups;
    }

    parseLinqOrders() {
        let orders = [];
        if (this.stream.match("order", true, true)) {
            this.stream.expect("by", true);
            do {
                let expression = this.parseExpression();
                let order = 1;
                if (this.stream.match(["desc", "asc"], false, true)) {
                    if ("desc" === this.stream.consume().getText()) {
                        order = -1;
                    }
                }
                orders.push(new LinqOrder(new Span(expression.getSpan(), this.stream.getPrev().getSpan()), expression, null, order));
            } while (this.stream.match(TokenType.Comma, true));
        }
        return orders;
    }

    parseLinqField() {
        let expression = this.parseExpression();
        if (this.stream.match(TokenType.Identifier, false) && !this.stream.match(linqKeywords, false, true)) {
            let alias = this.stream.expect(TokenType.Identifier).getSpan();
            return new LinqField(new Span(expression.getSpan(), alias), expression, alias.getText());
        }
        return new LinqField(expression.getSpan(), expression, null);
    }

    parseLinqFields() {
        let fields = [];
        do {
            let expression = this.parseExpression();

            if (this.stream.match(TokenType.Identifier, false) && !this.stream.match(linqKeywords, false, true)) {
                if (expression instanceof WholeLiteral) {
                    throw new ParseException("* 后边不能跟别名", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
                } else if (expression instanceof MemberAccess && expression.isWhole()) {
                    throw new ParseException(expression.getSpan().getText() + " 后边不能跟别名", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
                }
                let alias = this.stream.consume().getSpan();
                fields.push(new LinqField(new Span(expression.getSpan(), alias), expression, alias.getText()));
            } else {
                fields.push(new LinqField(expression.getSpan(), expression, null));
            }
        } while (this.stream.match(TokenType.Comma, true));    //,
        if (fields.length === 0) {
            throw new ParseException("至少要查询一个字段", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
        }
        return fields;
    }

    parseLinqJoins() {
        let joins = [];
        do {
            let isLeft = this.stream.match("left", false);
            let opeing = isLeft ? this.stream.consume().getSpan() : null;
            if (this.stream.match("join", true)) {
                opeing = isLeft ? opeing : this.stream.getPrev().getSpan();
                let target = this.parseLinqField();
                this.stream.expect("on");
                let condition = this.parseExpression();
                joins.push(new LinqJoin(new Span(opeing, this.stream.getPrev().getSpan()), isLeft, target, condition));
            }
        } while (this.stream.match(["left", "join"], false));
        return joins;
    }

    parseAccessOrCallOrLiteral(expectRightCurly) {
        let expression;
        let isString = false;
        if (expectRightCurly && this.stream.match("}", false)) {
            return null;
        } else if (this.stream.match("async", false)) {
            expression = this.parseAsync();
        } else if (this.stream.match("select", false, true)) {
            expression = this.parseSelect();
        } else if (this.stream.match(TokenType.Spread, false)) {
            expression = this.parseSpreadAccess();
        } else if (this.stream.match(TokenType.Identifier, false)) {
            expression = this.parseAccessOrCall(TokenType.Identifier);
        } else if (this.stream.match(TokenType.LeftCurly, false)) {
            expression = this.parseMapLiteral();
        } else if (this.stream.match(TokenType.LeftBracket, false)) {
            expression = this.parseListLiteral();
        } else if (this.stream.match(TokenType.StringLiteral, false)) {
            isString = true;
            expression = new Literal(this.stream.expect(TokenType.StringLiteral).getSpan(), 'java.lang.String');
        } else if (this.stream.match(TokenType.BooleanLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.BooleanLiteral).getSpan(), 'java.lang.Boolean');
        } else if (this.stream.match(TokenType.DoubleLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.DoubleLiteral).getSpan(), 'java.lang.Double');
        } else if (this.stream.match(TokenType.FloatLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.FloatLiteral).getSpan(), 'java.lang.Float');
        } else if (this.stream.match(TokenType.ByteLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.ByteLiteral).getSpan(), 'java.lang.Byte');
        } else if (this.stream.match(TokenType.ShortLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.ShortLiteral).getSpan(), 'java.lang.Short');
        } else if (this.stream.match(TokenType.IntegerLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.IntegerLiteral).getSpan(), 'java.lang.Integer');
        } else if (this.stream.match(TokenType.LongLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.LongLiteral).getSpan(), 'java.lang.Long');
        } else if (this.stream.match(TokenType.DecimalLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.DecimalLiteral).getSpan(), 'java.math.BigDecimal');
        } else if (this.stream.match(TokenType.RegexpLiteral, false)) {
            let token = this.stream.expect(TokenType.RegexpLiteral);
            let target = new Literal(token.getSpan(), 'java.util.regex.Pattern');
            expression = this.parseAccessOrCall(target);
        } else if (this.stream.match(TokenType.NullLiteral, false)) {
            expression = new Literal(this.stream.expect(TokenType.NullLiteral).getSpan(), 'null');
        } else if (this.linqLevel > 0 && this.stream.match(TokenType.Asterisk, false)) {
            expression = new WholeLiteral(this.stream.expect(TokenType.Asterisk).getSpan());
        } else if(this.stream.match(TokenType.Language, false)){
            expression = new LanguageExpression(this.stream.consume().getSpan(), this.stream.consume().getSpan());
        }
        if (expression == null) {
            throw new ParseException("Expected a variable, field, map, array, function or method call, or literal.", this.stream.hasMore() ? this.stream.consume().getSpan() : this.stream.getPrev().getSpan());
        }
        if (expression && isString && this.stream.match([TokenType.Period, TokenType.QuestionPeriod], false)) {
            this.stream.prev();
            expression = this.parseAccessOrCall(TokenType.StringLiteral);
        }
        return this.parseConverterOrAccessOrCall(expression);
    }

    async preprocessComplection(returnJavaType, defineEnvironment) {
        let env = {
            ...defineEnvironment,
            ...JavaClass.getAutoImportClass(),
            ...JavaClass.getAutoImportModule()
        }
        let expression;
        while (this.stream.hasMore()) {
            let token = this.stream.consume();
            var index = this.stream.makeIndex();
            try {
                if (token.type === TokenType.Identifier && token.getText() === 'var') {
                    let varName = this.stream.consume().getText();
                    if (this.stream.match(TokenType.Assignment, true)) {
                        let isAsync = this.stream.match("async", true);
                        let value = this.parseStatement();
                        env[varName] = isAsync ? "java.util.concurrent.Future" : await value.getJavaType(env);
                        if (!this.stream.hasMore()) {
                            expression = value;
                        }
                    }
                } else if (token.type === TokenType.Identifier && token.getText() === 'import') {
                    let varName;
                    let value;
                    if (this.stream.match(TokenType.Identifier, false)) {
                        varName = this.stream.consume().getText();
                        value = varName;
                    } else if (this.stream.match(TokenType.StringLiteral, false)) {
                        value = this.stream.consume().getText();
                        value = value.substring(1, value.length - 1);
                    }
                    let index = -1;
                    if (this.stream.match('as', true)) {
                        varName = this.stream.consume().getText();
                    } else {
                        index = value.lastIndexOf('.');
                        if (index > -1) {
                            varName = value.substring(index + 1)
                        }
                    }
                    if (varName) {
                        env[varName] = value;
                    }
                } else if (token.getTokenType() === TokenType.Assignment) {
                    let varName = this.stream.getPrev().getText()
                    let value = this.parseStatement();
                    env[varName] = await value.getJavaType(env);
                    if (!this.stream.hasMore()) {
                        expression = value;
                    }
                } else if (token.getTokenType() === TokenType.Identifier) {
                    this.stream.prev();
                    expression = this.parseAccessOrCall(token.getTokenType());
                }
            } catch (e) {
                this.stream.resetIndex(index);
                expression = null;
            }

        }
        if (returnJavaType) {
            return expression && await expression.getJavaType(env);
        }
        return env;
    }

    async completion(env) {
        var type = await this.preprocessComplection(true, env || {});
        return await JavaClass.loadClass(type);

    }
}

